﻿using System;
using System.Collections;

namespace ADTLists
{
    public class ADTGenericLinkedList<T> : ADT
    {
        ADTGenericLinkedListNode<T> head;
        ADTGenericLinkedListNode<T> tail;

        public ADTGenericLinkedList()
        {
            head = null;
            tail = null;
            size = 0;
        }

        public override object GetElementFromStart()
        {
            if (size > 0)
            {
                return head.data;
            }
            else
            {
                throw new Exception("Error while getting from start");
            }
        }

        public override object GetElementFromEnd()
        {
            if (size > 0)
            {
                return tail.data;
            }
            else
            {
                throw new Exception("Error while getting from end");
            }
        }

        public override object GetElementFromPosition(int position)
        {
            if (position < size && size > 0)
            {
                return GetInternal(position).data;
            }
            else
            {
                throw new Exception("Error while getting from position " + position);
            }
        }

        public override void DelFromStart()
        {
            if (size > 0)
            {
                RemoveInternal(head);
            }
            else 
            {
                throw new Exception("Error while removing from start");
            }
        }

        public override void DelFromEnd()
        {
            if (size > 0)
            {
                RemoveInternal(tail);
            }
            else
            {
                throw new Exception("Error while removing from end");
            }
        }

        public override void DelFromPosition(int position)
        {
            if (size > 0 && position < size)
            {
                RemoveInternal(GetInternal(position));
            }
            else
            {
                throw new Exception("Error while removing from position " + position);
            }
        }

        public override void AddToStart(object element)
        {
            if (size >= 0)
            {
                ADTGenericLinkedListNode<T> listElement = new ADTGenericLinkedListNode<T>((T)element);
                if (tail == null)
                {
                    tail = listElement;
                }
                else
                {
                    listElement.next = head;
                    head.previous = listElement;
                }
                head = listElement;
                size++;
            }
            else
            {
                throw new Exception("Error while adding the element to the start");
            }
        }

        public override void AddToEnd(object element)
        {
            if (size >= 0)
            {
                ADTGenericLinkedListNode<T> listElement = new ADTGenericLinkedListNode<T>((T)element);
                if (head == null)
                {
                    head = listElement;
                }
                else
                {
                    listElement.previous = tail;
                    tail.next = listElement;
                }
                tail = listElement;
                size++;
            }
            else
            {
                throw new Exception("Error while adding the element to the end");
            }
        }

        public override void AddToPosition(object element, int position)
        {
            if (position < size && size >= 0)
            {
                if (position == 0 && head == null)
                {
                    AddToStart(element);
                }
                else
                {
                    AddInternalBefore(GetInternal(position), (T)element);
                }
            }
            else
            {
                throw new Exception("Error while adding to position #" + position);
            }
        }

        public override void SetFirst(object element)
        {
            if (size > 0)
            {
                GetInternal(0).data = (T)element;
            }
            else
            {
                throw new Exception("Error while setting the 1st element");
            }
        }

        public override void SetLast(object element)
        {
            if (size > 0)
            {
                GetInternal(size - 1).data = (T)element;
            }
            else
            {
                throw new Exception("Error while setting the last element");
            }
        }

        public override void SetToPosition(object element, int position)
        {
            if (position >= 0 && size > 0 && position < size)
            {
                GetInternal(position).data = (T)element;
            }
            else
            {
                throw new Exception("Error while setting the element #" + position);
            }
        }
        
        public override void Clear()
        {
            ADTGenericLinkedListNode<T> temp;
            while (head != null)
            {
                temp = head;
                head = head.next;
                temp.previous = null;
                temp.next = null;
            }
            tail = null;
            size = 0;
            temp = null;
        }

        private ADTGenericLinkedListNode<T> GetInternal(int index)
        {
            if (size > index && index >= 0 && size > 0)
            {
                ADTGenericLinkedListNode<T> iterator;
                int DistanceFromHead = index;
                int DistanceFromTail = size - index - 1;
                if (DistanceFromHead < DistanceFromTail)
                {
                    iterator = head;
                    while (iterator != null && DistanceFromHead-- > 0)
                    {
                        iterator = iterator.next;
                    }
                }
                else
                {
                    iterator = tail;
                    while (iterator != null && DistanceFromTail-- > 0)
                    {
                        iterator = iterator.previous;
                    }
                }
                return iterator;
            }
            return null;
        }

        private void RemoveInternal(ADTGenericLinkedListNode<T> element)
        {
            if (element != null)
            {
                if (element == head)
                {
                    head = element.next;
                }
                if (element == tail)
                {
                    tail = element.previous;
                }
                if (element.previous != null)
                {
                    element.previous.next = element.next;
                }
                if (element.next != null)
                {
                    element.next.previous = element.previous;
                }
                size--;
            }
            else
            {
                throw new Exception("Error while removing");
            }
        }

        private ADTGenericLinkedListNode<T> AddInternalBefore(ADTGenericLinkedListNode<T> offset, T element)
        {
            if (offset != null)
            {
                ADTGenericLinkedListNode<T> listElement = new ADTGenericLinkedListNode<T>(element);
                listElement.previous = offset.previous;
                listElement.next = offset;
                if (offset.previous != null)
                {
                    offset.previous.next = listElement;
                }
                offset.previous = listElement;
                if (offset == head)
                {
                    head = listElement;
                }
                size++;
                return listElement;
            }
            else
            {
                throw new Exception("Error while adding an element");
            }
        }

        public override IEnumerator GetEnumerator()
        {
            throw new NotImplementedException();
        }
    }
}
